<?php

namespace App\Services;

use App\Contracts\SystemComponent;
use BadMethodCallException;
use Closure;
use Exception;
use Illuminate\Support\Collection;
use Route;

class ComponentService
{
    const HIERARCHY_LEVEL_THRESHOLD = 5;

    /** @var Collection */
    protected static $hierarchy;

    /**
     * @param $entity
     * @param Closure|null $callback
     * @param bool $bottomUp reverse the order of the Collection that is returned
     * @return Collection
     */
    public static function getOrderedComponentHierarchy($entity, $callback = null, bool $bottomUp = false): Collection
    {
        // Intialise the hierarchy
        static::$hierarchy = new Collection();

        // If the callback argument is not supplied or is not a valid callback, create a default callback that just
        // returns a single argument without doing anything to it.
        if (!is_callable($callback)) {
            $callback = function ($entity) {
                return $entity;
            };
        }

        // If the given entity is not a SystemComponent, we cannot iterate further up in the hierarchy because it may
        // implement the getParent() method. We just run the entity through the callback and return the Collection with
        // the result as the only element
        if (!($entity instanceof SystemComponent)) {
            return static::$hierarchy->push($callback($entity));
        }

        // Define the Collection method to use which will alter the order of the hierarchy in the Collection
        $collectionMethod = 'prepend';
        if ($bottomUp === true) {
            $collectionMethod = 'push';
        }

        // Extra defensive in case the method are ever removed/deprecated from the Collection class
        if (!method_exists(static::$hierarchy, $collectionMethod)) {
            throw new BadMethodCallException(
                "Member function $collectionMethod does not exist on the " . Collection::class . " object."
            );
        }

        static::generateHierarchyCollection($entity, $callback, $collectionMethod);
        return static::$hierarchy;
    }

    /**
     * Run the given entity through the given callback and replace the $entity variable with the result of calling the
     * entity's getParent() method until we hit an object that does not implement the SystemComponent contract, or
     * returns itself as a parent (User = top of the hierarchy) or we reach the hierarchy recursion limit
     *
     * @param $entity
     * @param Closure $callback
     * @param string $collectionMethod
     */
    protected static function generateHierarchyCollection($entity, Closure $callback, string $collectionMethod)
    {
        $level = 0;
        while ($entity instanceof SystemComponent && $entity->getParent() !== $entity
            && $level < static::HIERARCHY_LEVEL_THRESHOLD) {

            // Catch exceptions generated by the given closure and make the result null where an exception is caught
            try {
                $result = $callback($entity);
            } catch (Exception $e) {
                $result = null;
            }

            // Push the result onto the beginning of the Collection
            static::$hierarchy->$collectionMethod($result);
            $entity = $entity->getParent();

            $level++;
        }
    }

    /**
     * Get the title at the end of the breadcrumb for create and edit views, where the entity that is passed in is the
     * parent entity, so we need to construct the text from the route information
     *
     * @return string
     */
    public static function getBreadcrumbTextFromRoute(): string
    {
        list($entityType, $operation) = explode(".", Route::current()->getName());
        if (!isset($entityType, $operation) || !in_array($operation, ["create", "edit"])) {
            return '';
        }

        // Determine whether we are creating or editing
        $text = "New ";
        if ($operation === 'edit') {
            $text = "Edit ";
        }

        return $text . ucfirst($entityType);
    }
}